package com.zeddy.android.HarrisVoice;

/*
 * Filename: 	HarrisAndroidActivity.java
 * Classname: 	HarrisAndroidActivity
 * Author:		i.z.chirombe@wustl.edu
 * Description:	This class is the basis of Harris's ability to interact verbally with
 * 				with the human race. Its runs a server, that accepts PC based clients 
 * 				connection via TCP/IP over the USB interface. Upon initialization, it 
 * 				first checks to see if the there is a tablet running TouchPoint activity.
 * 				If there is, then it disables and defer all voice, and map requests to 
 * 				to the TouchPoint server, otherwise it handles those requests natively. 
 * 				In addition to the touchPoint abilities, this class also performs 
 * 				Facial Biometric signature computation one face at a time. 
 * 
 *  			For more information about Robot/Human interaction, refer to 
 *  				TouchPointActivity.
 */

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import com.zeddy.android.HarrisVoice.HarrisBlueTransport.BluetoothErrors;
import com.zeddy.android.HarrisVoice.HarrisMessage.HarrisMessageTypes;
import com.zeddy.android.HarrisVoice.HarrisTcpTransport.TcpErrors;

import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

/*
 * Author: Zeddy Chirombe - 
 */

public class HarrisAndroidActivity extends Activity implements TextToSpeech.OnInitListener {
	private static boolean DEBUG = true;
	private static final int CLIENT_MODE = 100;
	private static final int SERVER_MODE = 101;
	private static final String TAG = "BT:";
	
	private TextToSpeech textToSpeech = null;
	private boolean speechToText = true;
	private SpeechRecognizer speechRecognizer = null;
	
	private Button btnClient = null;
	private Button btnServer = null;
	private Button btnStartSvc = null;
	private Button btnTerminateSvc = null;
	
	private int logClounter = 0;
	private TextView edtNotepad = null;
	private int serverMode = 0;
	private boolean bt_activated = false;
	private Handler handler = null;
	
	private boolean tabletSupport = true;
	private BluetoothReader bluetoothReader = null;
	private HarrisBlueTransport harrisBlueTransport = null;
	private HarrisTcpTransport harrisTcpTransport = null;
	private AndroidServer androidServer = null;
	private boolean androidServerActive = false;
	
	private HarrisMessage PendingMessage = new HarrisMessage();
	private HarrisBiometrics harrisBiometrics = null;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        handler = new Handler();
          
        // Initialize text-to-speech. This is an asynchronous operation.
        textToSpeech = new TextToSpeech(this, this); 
        speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this); // TextToSpeech.OnInitListener
        
        // setup voice recognition - here 
        // Check to see if a recognition activity is present
        PackageManager pm = getPackageManager();
        List<ResolveInfo> activities = pm.queryIntentActivities(
                new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
        if (activities.size() == 0) {
     	   showPost("No Voice Recognizer Found");
     	  speechToText = false;
     	  finish();
        }
        showPost("Speech engine - OK");
        
        // application hooks setup - 
        btnClient = (Button) findViewById(R.id.btn_client_connect);
        btnServer = (Button) findViewById(R.id.btn_server_connect);
        btnStartSvc = (Button) findViewById(R.id.btn_start_service);
        btnTerminateSvc = (Button) findViewById(R.id.btn_terminate_service);
        
        edtNotepad = (TextView)findViewById(R.id.show_app_data);
        edtNotepad.setEnabled(false);
        
        // setup biometric engine - 
        harrisBiometrics = new HarrisBiometrics(handler,edtNotepad);
        showPost("Voice Biometrics - OK");
        
        // ---- After you build the table server, then this can  be activated -       
        // activate bluetooth object - 
        harrisBlueTransport = new HarrisBlueTransport(handler, edtNotepad);
        
        btnClient.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
            	if(serverMode != 0) {
            		showPost("BT Service is already set");
            		return;
            	}
            	serverMode = CLIENT_MODE;
            	//startDiscovery("Client");
            	btnServer.setEnabled(false);
            }
        });
        btnServer.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
            	if(serverMode != 0) {
            		showPost("BT Service is already set");
            		return;
            	}
            	serverMode = SERVER_MODE;
            	//startDiscovery("Server");   
            	btnClient.setEnabled(false);
            }
        });
        btnStartSvc.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {  
            	if(serverMode == 0) {
            		showPost("BT Service Mode is not set yet.");
            		return;
            	}
                //start server bluetooth-transport - 
                if(serverMode == SERVER_MODE) {
                	if(bt_activated) {
                		showPost("BT Service is already running");
                		return;
                	}
                    //start SpeechRecognizing facility -  
                	showPost("Starting Server tranport"); 
                    speechRecognizer.setRecognitionListener(new HarrisSpeechRecognizer());
                    showPost("Voice Recognizer - OK");
                    
                    // activate bluetooth object - Android Tablet mediator
                    harrisBlueTransport = new HarrisBlueTransport(handler, edtNotepad);
                    if(harrisBlueTransport.transSetup()!= BluetoothErrors.SUCCESS) {
                    	showPost("Error: bluetoothSetup() failed...No IPad support applicable..."+
                    			"info: To enable 'tabletSupport', pair a tableta nd restart this application. ");
                    	harrisBlueTransport = null;
                    	tabletSupport = false;
                    } else {
            			BluetoothErrors btTrans = harrisBlueTransport.connect();
            			if(btTrans != BluetoothErrors.SUCCESS) {
            				showPost("Error: BT_connect() - Trans for tabletHelper failed");
            				tabletSupport = false;
            				harrisBlueTransport.cancel();
            				harrisBlueTransport = null;
            			} else {
            				bluetoothReader = new BluetoothReader();
            				bluetoothReader.start();
            				tabletSupport = true;
            				showPost("info: tablet-helper is - ON");
            			}
                    }

                    //start tcp android server -  
                    harrisTcpTransport = new HarrisTcpTransport(handler, edtNotepad);
            		androidServer = new AndroidServer();
            		androidServer.start();   
            		
            		
                } 
                btnStartSvc.setEnabled(false);
            }
        });
        
        btnTerminateSvc.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
            	androidServerActive = false;
       		  	onDestroy();
            	finish();
            }
        }); 	
    }
    // Implements TextToSpeech.OnInitListener.
    public void onInit(int status) {
        // status can be either TextToSpeech.SUCCESS or TextToSpeech.ERROR.
        if (status == TextToSpeech.SUCCESS) {
            // Set preferred language to US English.
            // Note that a language may not be available, and the result will indicate this.
            textToSpeech.setLanguage(Locale.US);
        } else {
            // Initialization failed.
            showPost("Could not initialize TextToSpeech.");
        }  
    }

    public void onDestroy() {
    	/*
    	// notifying partner we are exiting..........
        HarrisMessage harrisMsg = new HarrisMessage();
        harrisMsg.MsgType = HarrisMessageTypes.ANDROID_UNDEFINED;
        harrisMsg.add_entry("zeddy");
        harrisMsg.add_entry("AndroidServer exiting.....");
        harrisBlueTransport.send_msg(harrisMsg.str());
        */
        
        textToSpeech.stop();
        textToSpeech.shutdown();
        speechRecognizer.cancel();
        speechRecognizer.destroy();
        handler = null;

        harrisBiometrics.cancel();
        harrisBiometrics = null;
        btnClient.destroyDrawingCache();
        btnServer.destroyDrawingCache();
        btnStartSvc.destroyDrawingCache();
        
        harrisTcpTransport.cancel();
        harrisTcpTransport = null;
        harrisBlueTransport.cancel();
        harrisBlueTransport = null;
        androidServer = null;
        
  	   	super.onDestroy();	      	
    }
    
	public void showPost(final String msg) {
		if(DEBUG) { Log.e(TAG,msg);}
		handler.post(new Runnable() {
			@Override
			public void run() {
				try {
					
					if(logClounter++ > 20) {
						edtNotepad.setText(msg);
						logClounter = 0;
					} else {
						edtNotepad.setText(msg+System.getProperty("line.separator")+edtNotepad.getText());
					}
				} catch (NullPointerException e) {
					//if(DEBUG) Log.e(TAG, "showPost()::"+e);
				} catch (Exception e) {
					//if(DEBUG) Log.e(TAG, "showPost()::"+e);
				}
			}	
		});
	}
	public void postReply(final String msg) {
		handler.post(new Runnable() {
			@Override
			public void run() {
				harrisTcpTransport.send_msg(msg);
			}	
		});
	}

	public void sleep(final int seconds) { // generic sleep
		try {
			Thread.sleep(seconds);
		} catch (InterruptedException e) {
			if(DEBUG) Log.e(TAG, "Main::"+e);
		}
	}
	public void sleep2(String msg) { // sleep seconds proportional to the message 
		try {
			Thread.sleep(msg.length()*100);
		} catch (InterruptedException e) {
			if(DEBUG) Log.e(TAG, "Main::"+e);
		}
	}
	private void tellme(String question) {
		try {
			//textToSpeech.speak(question, TextToSpeech.QUEUE_FLUSH,null);
			textToSpeech.speak(question, TextToSpeech.QUEUE_ADD,null);
		} catch (Exception e) { 
			if(DEBUG) Log.e(TAG, "Main::"+e);
		}
	}
	//---------- Bluetooth reader - 
	private class BluetoothReader extends Thread {
		HarrisMessage harrisMsg = new HarrisMessage();
		BluetoothErrors btTrans = null;
		public synchronized void run() {
			while(tabletSupport) {
				//showPost("waiting for bluetooth callback - ");
				btTrans = harrisBlueTransport.recv_msg(harrisMsg);
				if(btTrans == BluetoothErrors.SUCCESS) {
					//postReply(harrisMsg.str());
					if(harrisMsg.MsgType == HarrisMessageTypes.ANDROID_UNDEFINED) {
						showPost("Error recieving from TouchPoint.\nReseting connection...");
						showPost(harrisMsg.str());
						blueConnect();
						continue;
					}
					harrisTcpTransport.send_msg(harrisMsg.str());
					showPost(harrisMsg.str());
				} else {
					showPost("Error recieving from TouchPoint.\nReseting connection...");
					if(blueConnect()) {
						continue;
					}
				}
			}
		}
		public boolean blueConnect() {
			while(true) {
				if(harrisBlueTransport.connect() != BluetoothErrors.SUCCESS) {
					showPost("...still trying serverConnect...");
					continue;
				}
				break;
			}
			return false;
		}
	}

	//----------
	private class AndroidServer extends Thread {
		private Handler serverHandler = new Handler();
		private HarrisMessage harrisMsg = new HarrisMessage();
		public synchronized void run() {

			harrisMsg.data = "";
			
			TcpErrors tcpTrans = harrisTcpTransport.transSetup();
			if(tcpTrans != TcpErrors.SUCCESS) {
        		showPost("TCP-CONN-Failed...\nAndroidServer ...exiting");
        		return;
        	}
        	showPost("Tcp/Usg Transport - ok....");
			
        	androidServerActive = true;
			while(androidServerActive) {
				// Keep listening until exception occurs or a socket is returned	
				harrisMsg.data = "";
				tcpTrans = harrisTcpTransport.recv_msg(harrisMsg);
				//in case we attempt to ready before the socket is ready - 
				if(tcpTrans == TcpErrors.ZERO_BYTE_READ) {
					showPost("Zero Byte Read....try again");
					continue;
				}
	        	showPost("....trans-start....");
	        	if(tcpTrans == TcpErrors.ACCEPT_FAILED) {
	        		showPost("Transport Accept() - Malfunction...exiting");
	        		continue;
	        	}
	        	if((tcpTrans != TcpErrors.SUCCESS) || 
	        			(harrisMsg.MsgType == HarrisMessageTypes.ANDROID_UNDEFINED)) {
	        		showPost("Tranport Error : reseting Client Connection");
	        		while(harrisTcpTransport.listen() != TcpErrors.SUCCESS) {
	        			showPost("Transport Reseting failed...exiting");
	        			sleep(2);
	        			break;
	        		}
	        		showPost("Client connection - ok....\n....awaiting requests...");
	        		continue;
	        	}
	        	//showPost("dbg Msg - "+harrisMsg.data);
	        	String myMsg = harrisMsg.str();
	        	if(myMsg == "") {
	        		continue;
	        	}
	        	
	        	if(speechToText) {
					showPost(harrisMsg.str());	
					//Store this - so you can reconstruct this message from any callback - 
					PendingMessage.reset();
					PendingMessage.MsgType = harrisMsg.MsgType;
					PendingMessage.add_entry(harrisMsg.entry(0));
					PendingMessage.add_entry(harrisMsg.entry(1));
					switch(harrisMsg.MsgType) {
					case ANDROID_BROADCAST:		// one way voice message - 
						if(tabletSupport) {
							harrisBlueTransport.send_msg(harrisMsg.str());
						} else {
							tellme(harrisMsg.entry(2)); // run text-to-speech to ask your question.
							showPost("\n....trans-stop....");
						}
						postReply(PendingMessage.str());
						break;
					case ANDROID_QUESTION: 		// Question human for help - 
						if(tabletSupport) {
							harrisBlueTransport.send_msg(harrisMsg.str());
						} else {
							tellme(harrisMsg.entry(2)); // run text-to-speech to ask your question.
							activateHarriesEars(harrisMsg.entry(2));
						}
						break;
					case ANDROID_GRID_LOCATION:	// Ask human for a desired map location - 
						if(tabletSupport) {
							harrisBlueTransport.send_msg(harrisMsg.str());
						} else {
							//tellme(harrisMsg.entry(2)); // run text-to-speech to ask your question.
							PendingMessage.add_entry("Error, No TabletSupport ..");
							postReply(PendingMessage.str());
						}
						break;
					case ANDROID_BIOMETRICS: 	// get a biometric signature
						harrisBiometrics.faceDetect(PendingMessage, harrisMsg.entry(2));
						//harrisTcpTransport.send_msg(PendingMessage.str());
						//showPost(PendingMessage.str());
						postReply(PendingMessage.str());
						break;
					default:
						String undefined = harrisMsg.MsgType+":android:message malformed..discarding...";
						//harrisTcpTransport.send_msg(undefined+"\n");
						postReply(PendingMessage.str());
						showPost(undefined);
					}
				} else {
					showPost("Error: No Speech Recognizer present...");
				}
				harrisMsg.data = "";
			}		
		}
		public void activateHarriesEars(String question) {
			//start voice recognition activity
			serverHandler.postDelayed(new Runnable() { 
				public void run() { 
					showPost("...start-recording...");
					beepEofSpeech();
					startVoiceRecording();
				} 
			}, question.length()*100);
		}
		//delete all completed bio faces 
		public void deleteFaces() {
			String path = "/sdcard/faces";
		    File file = new File(path);

		    if (file.exists()) {
		        String deleteCmd = "rm -r " + path + "/*";
		        Runtime runtime = Runtime.getRuntime();
		        try {
		            runtime.exec(deleteCmd);
		        } catch (IOException e) { }
		    }
		}

		public void sleep(final int seconds) { // generic sleep
			try {
				Thread.sleep(seconds);
			} catch (InterruptedException e) {
				if(DEBUG) Log.e(TAG, "Main::"+e);
			}
		}
		
        /** Will cancel the listening socket, and cause the thread to finish */
        @SuppressWarnings("unused")
		public void cancel() {
        	close();
        }
        public void close() {
        	harrisMsg.reset();
        	harrisMsg = null;
        	serverHandler = null;
        	bluetoothReader.destroy();
        	bluetoothReader = null;
        }
	}
	//----------
    
	//-------------------------------------------------------------------
	public void beepEofSpeech () {
		Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
		MediaPlayer mMediaPlayer = new MediaPlayer();
		try {
			mMediaPlayer.setDataSource(this, soundUri);
			AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
			if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
				mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
				mMediaPlayer.setLooping(false);
				mMediaPlayer.prepare();
				mMediaPlayer.start();
			}
		} catch (SecurityException e) {
			showPost("pSound"+e);
		} catch (IllegalStateException e) {
			showPost("pSound"+e);
		} catch (IOException e) {
			showPost("pSound"+e);
		} catch (Exception e) {
			showPost("pSound"+e);
		}
		mMediaPlayer = null;
		soundUri = null;
	}
	/*
	 *  Voice Recognition starts here - 
     * Fire an intent to start the voice recognition activity.
     */
    private void startVoiceRecording() {
    	try {
        	Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);        
            intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
            intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,"Harris Voice Recognition...");
            intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS,2); 
            speechRecognizer.startListening(intent); 
            //speechRecognizer.
    	} catch (ActivityNotFoundException e) {
    		showPost("VOICE: Nothing heard, ask agin...\n"+e);
    	}
    }
    
    class HarrisSpeechRecognizer implements RecognitionListener          
    {
    	/* Seech Recognizer Error Codes - 
    		ERROR_NETWORK_TIMEOUT
    		ERROR_NETWORK
    		ERROR_AUDIO
    		ERROR_SERVER
    		ERROR_CLIENT
    		ERROR_SPEECH_TIMEOUT
    		ERROR_NO_MATCH
    		ERROR_RECOGNIZER_BUSY
    		ERROR_INSUFFICIENT_PERMISSIONS
    		*/
    	public HarrisSpeechRecognizer() {
    	}
    	public void onReadyForSpeech(Bundle params)
        {
        	//showPost("onReadyForSpeech");
        }
        public void onBeginningOfSpeech()
        {
        	//showPost("onBeginningOfSpeech");
        }
        public void onRmsChanged(float rmsdB)
        {
        	//showPost("onRmsChanged");
        }
        public void onBufferReceived(byte[] buffer)
        {
        	//showPost("onBufferReceived");
        }
        public void onEndOfSpeech()
        {
        	//showPost("onEndofSpeech");
        }
        public void onError(int error)
        {
        	String errorMsg = "error( "+error+" ) - ";
        	if(error == 2) {
        		errorMsg += "Android - without Internet, I am like fish with no water: Please activate G4 or wifi";
        	} else {
        		 errorMsg += "- speech recong problem";
        	}
        	PendingMessage.add_entry(""+error);
        	PendingMessage.add_entry(errorMsg);
        	//harrisTcpTransport.postReply(PendingMessage.str());
        	//harrisTcpTransport.send_msg(PendingMessage.str());
        	postReply(PendingMessage.str());

            showPost(errorMsg);
        }
        public void onResults(Bundle results)                   
        {
        	ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
            String voiceMsg = "VOICE::("+matches.size()+")[ "+matches.get(0)+" ] ";
        	PendingMessage.add_entry("0");
        	PendingMessage.add_entry(matches.get(0));
        	//harrisTcpTransport.postReply(PendingMessage.str());
        	//harrisTcpTransport.send_msg(PendingMessage.str());
        	postReply(PendingMessage.str());
            showPost(voiceMsg);
        }
        public void onPartialResults(Bundle partialResults)
        {
        	//showPost("onPartialResults");
        }
        public void onEvent(int eventType, Bundle params)
        {
        	//showPost("onEvent " + eventType);
        }
    }
}